<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>虚拟列表跑马灯</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        .virtual-box {
            width: 200px;
            height: 360px;
            border: 1px solid #ccc;
            margin: 10px auto;
            position: relative;
            overflow: auto;
        }

        .virtual-list {
            position: absolute;
            width: 100%;
            height: 100%;
            top: 0;
            left: 0;
        }

        .virtual-item {
            padding: 10px;
            border-bottom: 1px solid #ccc;
        }
        /* 隐藏滚动条 */
        ::-webkit-scrollbar {
            width: 0px;
            height: 0px;
        }
    </style>
</head>

<body>
    <div class="virtual-box">
        <!--  虚拟滚动条  -->
        <div class="virtual-scrollbar"></div>
        <div class="virtual-list">
            <div class="virtual-item">
                <p>标题</p>
                <p>内容</p>
            </div>
        </div>
    </div>
    <script>
        let data = []
        for (let i = 1; i <= 100; i++) {
            data.push({
                id: i,
                title: "item-" + i,
                content: "测试"
            })
        }
        vitrualAutoScroll(data)
        function vitrualAutoScroll (data) {
            // 获取相关容器
            const virtualBox = document.querySelector(".virtual-box")
            const scrollbar = document.querySelector(".virtual-scrollbar")
            const virtualList = document.querySelector(".virtual-list")
            /**
             * 不知道子内容高度情况下实现思路
             * 1、假设一个高度
             * 2、用数组维护这个高度，当渲染第一次之后把真实高度缓存起来
             * 
             * */
            // 假设高度
            let itemHeight = 50
            // 设置全局变量 维护每一个子项的高度等信息
            let positions = data.map((item, index) => {
                return {
                    index,
                    height: itemHeight
                }
            })
            // 滚动容器高度
            let listHeight = virtualList.offsetHeight
            // 计算一屏渲染数量
            function calcViewCount () {
                let viewCount = 0
                let height = positions[0].height
                while (height < virtualList.offsetHeight) {
                    viewCount++;
                    height += positions[viewCount].height
                }
                return viewCount
            }
            let count = calcViewCount()
            let start = 0 // 渲染的开始位置
            let end = count // 渲染的结束位置

            // 虚拟滚动条的高度计算
            let scrollBarHeight = 0;
            function setScrollBarHeight () {
                let height = positions.reduce((total, item) => {
                    return total += item.height
                }, 0)
                scrollBarHeight = height // 另存一份滚动条高度
                scrollbar.style.height = height + "px"
            }
            setScrollBarHeight()

            // 渲染内容
            let isRenderEnd = false
            function render (start, end) {
                if (end > data.length) {
                    end = data.length
                    isRenderEnd = true
                }
                let str = ""
                for (let i = start; i < end; i++) {
                    str += `
                <div class="virtual-item">
                    <p>${data[i].title}</p>
                    <p>${data[i].content}</p>
                </div>
                `
                }
                virtualList.innerHTML = str
                // 计算子内容真实高度 -- 滚动完所有数据后 不再需要重新计算
                if (!isRenderEnd) {
                    let children = [...virtualList.children]
                    children.forEach((item, index) => {
                        if (index >= start && index <= end) {
                            let height = item.offsetHeight
                            positions[index].height = height
                        }
                    })
                }
                // 重新计算一屏可显示的数量
                calcViewCount()
                // 重新 虚拟滚动条的高度计算 - 滚动完所有数据后不需要再重新计算
                if (!isRenderEnd) {
                    setScrollBarHeight()
                }
            }
            render(start, end)
            // console.log(positions);

            function scrollFn () {

                let scrollTop = Math.floor(this.scrollTop);

                // 计算滚动离开屏幕的数量
                let scrollCount = 0
                function calcLeaveViewCount () {
                    let itemsHeight = positions[scrollCount].height
                    // scrollTop/itemsHeight 大于1 说明已经滚动到下一项内容了
                    while (scrollTop / itemsHeight >= 1) {
                        scrollCount++
                        itemsHeight += positions[scrollCount]?.height;
                    }
                }
                calcLeaveViewCount()
                // 计算开始和结束的位置
                start = scrollCount
                // count*2 默认渲染2屏的数据
                end = count * 2 + start
                render(start, end)

                // 滚动条计算偏移量 让列表回到原来的位置
                startOffset = this.scrollTop;
                virtualList.style.transform = `translateY(${startOffset}px)`

            }

            // 监听滚动
            virtualBox.addEventListener("scroll", scrollFn)

            // 自动滚动
            let timer = null
            function autoScroll () {
                // 如果滚动内容不足以产生滚动条，则不执行滚动
                if (virtualBox.scrollHeight <= virtualBox.clientHeight) {
                    return
                }
                // 设置定时器，每隔一段时间增加scrollTop的值
                timer = setInterval(() => {
                    if (virtualBox.scrollTop + virtualBox.clientHeight >= virtualBox.scrollHeight) {
                        // 如果滚动到底部，重置scrollTop
                        virtualBox.scrollTop = 0;
                    } else {
                        // 否则，增加scrollTop的值，产生滚动效果
                        virtualBox.scrollTop++;
                    }
                }, 60 / 1000);
            }
            autoScroll ()

            // 鼠标移入 暂停滚动
            virtualBox.addEventListener("mouseenter", function(){
                clearInterval(timer);
            })
            // 鼠标离开 继续滚动
            virtualBox.addEventListener("mouseleave", function(){
                autoScroll ()
            })
        }
    </script>
</body>

</html>